package com.gitee.wsl.mathematics.number

import com.gitee.wsl.ext.number.approx
import kotlin.reflect.KClass

/**
 * Describes the type of elements stored in a [NDArray].
 *
 * @param nativeCode an integer value of the type. Required to define the type in JNI.
 * @param itemSize size of one ndarray element in bytes.
 * @param clazz [KClass] type.
 *
 * @property ByteDataType byte.
 * @property ShortDataType short.
 * @property IntDataType int.
 * @property LongDataType long.
 * @property FloatDataType float.
 * @property DoubleDataType double.
 * @property ComplexFloatDataType complex float.
 * @property ComplexDoubleDataType complex double.
 */
 enum class DataType( val nativeCode: Int,  val itemSize: Int,  val clazz: KClass<out Any>) {
    ByteDataType(1, 1, Byte::class),
    ShortDataType(2, 2, Short::class),
    IntDataType(3, 4, Int::class),
    LongDataType(4, 8, Long::class),
    FloatDataType(5, 4, Float::class),
    DoubleDataType(6, 8, Double::class);
    //ComplexFloatDataType(7, 8, ComplexFloat::class),
    //ComplexDoubleDataType(8, 16, ComplexDouble::class);

     fun isNumber(): Boolean = when (nativeCode) {
        1, 2, 3, 4, 5, 6 -> true
        else -> false
    }

     fun isComplex(): Boolean = !isNumber()

     companion object {

        /**
         * Returns [DataType] by [nativeCode].
         */
         fun of(i: Int): DataType {
            return when (i) {
                1 -> ByteDataType
                2 -> ShortDataType
                3 -> IntDataType
                4 -> LongDataType
                5 -> FloatDataType
                6 -> DoubleDataType
                //7 -> ComplexFloatDataType
                //8 -> ComplexDoubleDataType
                else -> throw IllegalStateException("One of the primitive types indexes was expected, got $i")
            }
        }

        /**
         * Returns [DataType] by class of [element].
         */
        @Suppress( "nothing_to_inline")
         inline fun <T> of(element: T): DataType {
            element ?: throw IllegalStateException("Element is null cannot find type")
            return when (element!!::class) {
                Byte::class -> ByteDataType
                Short::class -> ShortDataType
                Int::class -> IntDataType
                Long::class -> LongDataType
                Float::class -> FloatDataType
                Double::class -> DoubleDataType
                //ComplexFloat::class -> ComplexFloatDataType
                //ComplexDouble64::class -> ComplexDoubleDataType
                //ComplexDouble32::class -> ComplexDoubleDataType
                else -> throw IllegalStateException("One of the primitive types was expected, " +
                        "got ${element!!::class.simpleName}")
            }
        }


        /**
         * Returns [DataType] by [KClass] of [type]. [T] is `reified` type.
         */
         inline fun <reified T : Any> ofKClass(type: KClass<out T>): DataType = when (type) {
            Byte::class -> ByteDataType
            Short::class -> ShortDataType
            Int::class -> IntDataType
            Long::class -> LongDataType
            Float::class -> FloatDataType
            Double::class -> DoubleDataType
            //ComplexFloat::class -> ComplexFloatDataType
            //ComplexDouble::class -> ComplexDoubleDataType
            else -> throw IllegalStateException("One of the primitive types was expected, got ${type.simpleName}")
        }
    }

    override fun toString(): String {
        return "DataType(nativeCode=$nativeCode, itemSize=$itemSize, class=$clazz)"
    }
}


@PublishedApi
internal inline fun <reified T : Number> Number.toPrimitiveType(): T = when (T::class) {
    Byte::class -> this.toByte()
    Short::class -> this.toShort()
    Int::class -> this.toInt()
    Long::class -> this.toLong()
    Float::class -> this.toFloat()
    Double::class -> this.toDouble()
    else -> throw Exception("Type not defined.")
} as T

@Suppress("UNCHECKED_CAST")
 fun <T : Number> Number.toPrimitiveType(dtype: DataType): T = when (dtype.nativeCode) {
    1 -> this.toByte()
    2 -> this.toShort()
    3 -> this.toInt()
    4 -> this.toLong()
    5 -> this.toFloat()
    6 -> this.toDouble()
    else -> throw Exception("Type not defined.")
} as T

 operator fun <T : Number> Number.compareTo(other: T): Int {
    return when {
        this is Float && other is Float -> this.compareTo(other)
        this is Double && other is Double -> this.compareTo(other)
        this is Int && other is Int -> this.compareTo(other)
        this is Long && other is Long -> this.compareTo(other)
        this is Short && other is Short -> this.compareTo(other)
        this is Byte && other is Byte -> this.compareTo(other)
        else -> this.toDouble().compareTo(other.toDouble())
    }
}

infix fun <T : Number> Number.approx(other: T): Boolean {
    return when {
        this is Float && other is Float -> this approx other
        this is Double && other is Double -> this approx other
        this is Int && other is Int -> this == other
        this is Long && other is Long -> this == other
        this is Short && other is Short -> this == other
        this is Byte && other is Byte -> this == other
        this is Float  -> this approx other.toFloat()
        this is Double  -> this approx other.toDouble()
        else -> this == other
    }
}

